package com.tiantian.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.tiantian.common.constant.EnrollResultConstants;
import com.tiantian.common.constant.JudgeConstants;
import com.tiantian.common.constant.SystemConfigConstants;
import com.tiantian.common.core.domain.SysOperLog;
import com.tiantian.common.core.domain.dto.NewcomerDTO;
import com.tiantian.common.core.domain.entity.SysJudger;
import com.tiantian.common.core.domain.entity.SysUser;
import com.tiantian.common.enums.BusinessType;
import com.tiantian.common.utils.redis.RedisUtils;
import com.tiantian.file.service.JxFileService;
import com.tiantian.system.service.ISysOperLogService;
import com.tiantian.system.service.ISysUserService;
import com.tiantian.topic.domain.dto.QueryReplySubDTO;
import com.tiantian.topic.domain.entity.SysReplay;
import com.tiantian.topic.domain.vo.SubInfoVO;
import com.tiantian.topic.service.SysJudgeService;
import com.tiantian.topic.service.SysReplyService;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 定时任务
 *
 * @author tiantian
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class SysJobService {

    private final JxFileService jxFileService;
    private final ISysUserService sysUserService;
    private final SysJudgeService sysJudgeService;
    private final SysReplyService sysReplyService;
    private final ISysOperLogService sysOperLogService;


    private static final String DRAWING_BED_BUCKET_NAME = "jx-welcome-drawing-bed-minio";

    /**
     * 过期文件清理
     *
     * <p>执行时间: 每日 凌晨2:00执行 CRON: 0 0 2 * * ?</p>
     */
    @XxlJob("clearExpiredFile")
    public void clearExpiredFile() {
        log.info("clearExpiredFile 文件清理开始执行");
        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, -1);
        Date lastMonthDate = calendar.getTime();
        // 获取出上个月的[yyyy/MM/dd]的字符串形式
        String lastMonthPrefix = format.format(lastMonthDate);
        // 扫描图床: jx-welcome-drawing-bed-minio
        // 找到所有以 yyyy/MM/dd 开头的文件
        List<Item> items = jxFileService.listObjects(DRAWING_BED_BUCKET_NAME, lastMonthPrefix);
        if (CollUtil.isEmpty(items)) {
            return;
        }
        // 进行删除
        for (Item item : items) {
            jxFileService.removeObject(DRAWING_BED_BUCKET_NAME, item.objectName());
        }
    }

    /**
     * <p>将新人分配给判题人 CRON: 手动执行一次</p>
     *
     * <p>执行时间 在答题结束1小时后执行一次 CRON: 手动执行一次</p>
     */
    @XxlJob("allotNewcomer")
    public void allotNewcomer() {
        log.info("allotNewcomer 开始分配判题人");
        // 获取出所有新人
        List<SysUser> newcomerList = sysUserService.selectPageUserList(null);
        // 过滤掉从未答题的新人
        List<SysUser> filteredNewcomerList = filterNotReplyUsers(newcomerList);
        // 获取出所有判题人
        List<SysJudger> sysJudgerList = sysJudgeService.selectJudgerList(null);
        // 进行分配
        assignNewcomerToJudger(filteredNewcomerList, sysJudgerList);
        // 批量更新至数据库
        int count = sysJudgeService.allotNewcomer(sysJudgerList);
        int status = count > 0 ? 0 : 1;
        // ============记录日志============
        SysOperLog operLog = new SysOperLog();
        operLog.setTitle("定时任务分配判题人");
        operLog.setMethod("com.tiantian.job.SysJobService.allotNewcomer()");
        operLog.setOperName("系统");
        operLog.setOperIp("10.226.8.14:/xxl-job");
        operLog.setStatus(status);
        operLog.setBusinessType(BusinessType.ALLOT_NEWCOMER.ordinal());
        operLog.setOperParam(sysJudgerList.toString());
        operLog.setRequestMethod("JOB");
        operLog.setOperTime(LocalDateTime.now());
        sysOperLogService.insertOperLog(operLog);

        // 获取出所有答题记录 设置判题人
        setAllJudgerForNewcomer();
    }

    /**
     * <p>将从未答题的用户总分设置为0 </p>
     *
     * <p>执行时间 全部完成评分后 CRON: 手动执行一次</p>
     */
    @XxlJob("calcTotalScore")
    public void calcTotalScore() {
        // 查询出所有的新人
        List<SysUser> sysUsers = sysUserService.selectPageUserList(null);
        List<Long> zeroUserIds = new ArrayList<>();
        for (SysUser sysUser : sysUsers) {
            Long userId = sysUser.getUserId();
            int countReply = sysReplyService.countReply(userId);
            // 答题记录数小于零
            if (countReply <= 0) {
                sysUserService.setNewcomerTotalScore(userId, JudgeConstants.ZERO);
                zeroUserIds.add(userId);
            }
        }
        // ============记录日志============
        SysOperLog operLog = new SysOperLog();
        operLog.setTitle("定时任务分配判题人");
        operLog.setOperName("系统");
        operLog.setMethod("com.tiantian.job.SysJobService.allotNewcomer()");
        operLog.setStatus(0);
        operLog.setBusinessType(BusinessType.ALLOT_NEWCOMER.ordinal());
        operLog.setJsonResult(zeroUserIds.toString());
        operLog.setRequestMethod("JOB");
        operLog.setOperTime(LocalDateTime.now());
        sysOperLogService.insertOperLog(operLog);
    }

    /**
     * <p>删除协会内用户的作答记录 </p>
     *
     * <p>执行时间: 每日 凌晨2:00执行 CRON: 0 0 2 * * ?</p>
     */
    @XxlJob("deleteAdminReplys")
    public void deleteAdminReplys() {
        sysReplyService.deleteAdminReplys();
        // ============记录日志============
        SysOperLog operLog = new SysOperLog();
        operLog.setTitle("清除工作人员作答记录");
        operLog.setOperName("系统");
        operLog.setMethod("com.tiantian.job.SysJobService.deleteAdminReplys()");
        operLog.setStatus(0);
        operLog.setBusinessType(BusinessType.DEL_ADMIN_REPLYS.ordinal());
        operLog.setRequestMethod("JOB");
        operLog.setOperTime(LocalDateTime.now());
        sysOperLogService.insertOperLog(operLog);
    }

    /**
     * <p>将低于分数线的用户 设置录取未通过状态</p>
     * <p>将高于分数线的用户 设置录取通过状态</p>
     *
     * <p>执行时间: 每日 凌晨2:00执行 CRON: 0 0 2 * * ?</p>
     */
    @XxlJob("setStatus")
    public void setStatus() {
        Map<String, Integer> scoreLineMap = RedisUtils.get(SystemConfigConstants.SYSTEM_CONFIG_INTERVIEW_SCORE_LINE, HashMap.class);
        Integer scoreLine = scoreLineMap.get("scoreLine");
        List<String> notPassList = new ArrayList<>();
        notPassList.add("未通过名单");
        List<SysUser> sysUserList = sysUserService.selectPageUserList(null);
        for (SysUser user : sysUserList) {
            Integer totalScore = user.getTotalScore();
            if (ObjUtil.isNotNull(totalScore)) {
                if (totalScore >= scoreLine) {
                    log.info("录取状态设置通过" + user.getStudentId() + ":" + user.getNickName());
                    user.setEnrollStatus(EnrollResultConstants.ENROLL_PASS);
                } else {
                    user.setEnrollStatus(EnrollResultConstants.ENROLL_NOT_PASS);
                    notPassList.add(user.getStudentId() + ":姓名" + user.getNickName());
                }
                NewcomerDTO newcomerDTO = new NewcomerDTO();
                newcomerDTO.setUserId(user.getUserId());
                newcomerDTO.setEnrollStatus(user.getEnrollStatus());
                // 更新至数据库
                sysUserService.updateNewcomer(newcomerDTO);
            }
        }
        // 查询出未答题的用户 设置状态为未录取
        QueryReplySubDTO queryReplySubDTO = new QueryReplySubDTO();
        queryReplySubDTO.setIsSub(false);
        List<SubInfoVO> subInfoVOList = sysJudgeService.selectSubInfo(queryReplySubDTO);
        for (SubInfoVO subInfoVO : subInfoVOList) {
            Long userId = subInfoVO.getUserId();
            NewcomerDTO newcomerDTO = new NewcomerDTO();
            newcomerDTO.setUserId(userId);
            newcomerDTO.setEnrollStatus(EnrollResultConstants.ENROLL_NOT_PASS);
            sysUserService.updateNewcomer(newcomerDTO);
        }
        // ============记录日志============
        SysOperLog operLog = new SysOperLog();
        operLog.setTitle("设置状态录取");
        operLog.setOperName("系统");
        operLog.setMethod("com.tiantian.job.SysJobService.setNotPassStatus()");
        operLog.setStatus(0);
        operLog.setJsonResult(notPassList.toString());
        operLog.setBusinessType(BusinessType.SET_ENR_STATUS.ordinal());
        operLog.setRequestMethod("JOB");
        operLog.setOperTime(LocalDateTime.now());
        sysOperLogService.insertOperLog(operLog);
    }

    /**
     * 将新人平均分配给判题人
     *
     * @param newcomerList 新人集合
     * @param judgerList   判题人集合
     */
    public void assignNewcomerToJudger(List<SysUser> newcomerList, List<SysJudger> judgerList) {
        // 全部重置(防止分配重复)
        for (SysJudger sysJudger : judgerList) {
            sysJudger.setReplyUsers(new ArrayList<>());
            sysJudger.setReplyIds(new ArrayList<>());
        }

        // 打乱顺序
        Collections.shuffle(newcomerList);
        Collections.shuffle(judgerList);
        if (newcomerList.size() > judgerList.size()) {
            for (int i = 0; i < newcomerList.size(); i++) {
                judgerList.get(i % judgerList.size())           // 获取判题人
                        .getReplyUsers()                        // 获取被判新人集合
                        .add(newcomerList.get(i).getUserId());  // 添加新人Id
            }
        } else {
            int newcomerCount = newcomerList.size();
            int judgerCount = judgerList.size();
            // 每个判题人平均分配的新人数
            int newcomerPerJudger = newcomerCount / judgerCount;
            // 多余新人的数量
            int remainingNewcomers = newcomerCount % judgerCount;
            for (int i = 0; i < newcomerPerJudger * judgerCount; i++) {
                judgerList.get(i % judgerList.size())
                        .getReplyUsers()
                        .add(newcomerList.get(i).getUserId());
            }
            for (int i = 0; i < remainingNewcomers; i++) {
                // 将多余的新人分配给前面的判题人
                judgerList.get(i % judgerList.size())
                        .getReplyUsers()
                        .add(newcomerList.get(newcomerPerJudger * judgerCount + i).getUserId());
            }

        }
    }

    /**
     * 过滤答题数量为0的新人用户
     *
     * @param newcomerList 新人用户集合
     */
    private List<SysUser> filterNotReplyUsers(List<SysUser> newcomerList) {
        List<SysUser> filteredNewcomers = new ArrayList<>();
        for (SysUser sysUser : newcomerList) {
            Long userId = sysUser.getUserId();
            if (sysReplyService.countReply(userId) <= 0) {
                continue;
            }
            filteredNewcomers.add(sysUser);
        }

        // 方法二使用 iterator 边遍历边删除
        /*Iterator<SysUser> iterator = newcomerList.iterator();
        while (iterator.hasNext()){
            SysUser sysUser = iterator.next();
            Long userId = sysUser.getUserId();
            int countReply = sysReplyService.countReply(userId);
            if (countReply > 0) {
                iterator.remove();
            }
        }*/
        return filteredNewcomers;
    }

    /**
     * 将所有的答题记录设置判题人Id
     */
    private void setAllJudgerForNewcomer() {
        List<SysJudger> sysJudgerList = sysJudgeService.selectJudgerList(null);
        // 获取出所有判题人
        for (SysJudger sysJudger : sysJudgerList) {
            // 获取出该判题人所有被分配到的用户Id
            List<Long> replyUsers = sysJudger.getReplyUsers();
            Long currUserId = sysJudger.getUserId();
            String judgerName = sysJudger.getJudgerName();
            for (Long userId : replyUsers) {
                // 查询出该用户的所有答题记录
                List<SysReplay> sysReplayList = sysReplyService.selectByUserId(userId);
                List<String> replyIdList = new ArrayList<>();
                for (SysReplay sysReplay : sysReplayList) {
                    replyIdList.add(sysReplay.getReplyId());
                    // 设置判题人并更新到数据库中
                    sysReplay.setJudgerId(currUserId);
                    sysReplay.setJudger(judgerName);
                    sysReplay.setUpdateBy("系统定时任务");
                    sysReplay.setUpdateTime(LocalDateTime.now());
                    sysReplyService.updateByReplyId(sysReplay);
                }
                // 设置判题人的答题数量
                sysJudger.setReplyIds(replyIdList);
                sysJudgeService.allotReplyIds(sysJudger);
            }
        }
    }

}
